ZK Using Grinder 3.0
Gerard Cristofol, Project Manager, Indra Sistemas S.A.
July 25, 2007
ZK Framework 2.4.1
- Grinder 3.0 beta
Abstract
This document presents a way to use Grinder Framework to load test ZK Applications. Also proposes an IdGenerator (New Features of ZK 2.4.1 ) implementation that keeps Grinder scripts simple
The Issue
Before 2.4.1 was released, the dynamic id assignment gave automated test tools a hard time. Mostly cause stress tools record navigation and events and don’t expect component ids to change after that.
The Solution
We’ll solve the problem (thanks to 2.4.1 new feature) developing an IdGenerator, witch generates suitable ids for the test tool. Such ids are easy to handle within Grinder and stay the same between sessions. That being solved, we will make desktop ids travel in the response so Grinder Worker threads can avail it.
The ZK Side
Build a ZUL Page
First we’ll need a ZK WebApp where the extra components are to be added. I’ve used NetBeans (First ZK application with NetBeans ) to build an application that’s not particularly more complex than a helloworld. In this particular case:
<?xml version="1.0" encoding="UTF-8"?>
<?page title="ZK::Hello Grinder!"?>
<window title="My First Testeable Window" border="normal" width="200px">
Hello, (Grinder) World!
<textbox id="caption" value="x"/>
<button label="add x">
<attribute name="onClick">{
caption.value = caption.value + "x";
System.out.println("I've been pressed! from " +caption.desktop.id);
}</attribute>
</button>
<textbox id="thecaption2"/>
<intbox id="theinteger"/>
</window>
The only remarkable chunk of code is the onClick event for the button. That’s the event we’ll try to invoke from Grinder. The other components are there just for you to see how component ids are built
IdGenerator
Next we will need a implementation of IdGenerator, here is my suggestion
public class GrinderIdGenerator extends DHtmlLayoutServlet implements org.zkoss.zk.ui.sys.IdGenerator {
private static AtomicInteger _desktop = new AtomicInteger();
private static ThreadLocal _page = new ThreadLocal();
private static ThreadLocal _response = new ThreadLocal();
@SuppressWarnings("unchecked")
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
_response.set(response);//soon to be used...
super.doGet(request, response);
}
@SuppressWarnings("unchecked")
public String nextComponentUuid(Desktop desktop, Component component) {
//NOTE: Limited to one page per desktop
Page page = (Page) desktop.getPages().iterator().next();
HttpServletRequest hsr = (HttpServletRequest) page.getDesktop().getExecution().getNativeRequest();
String pageURI = hsr.getRequestURI();
AtomicInteger ai = (AtomicInteger)_page.get();
String compid = pageURI +"_"+ component.getClass().getName() +"_"+ ai.getAndIncrement();
System.out.println("component id: " + compid);
return compid;
}
@SuppressWarnings("unchecked")
public String nextPageUuid(Page page) {
HttpServletRequest hsr = (HttpServletRequest) page.getDesktop().getExecution().getNativeRequest();
_page.set(new AtomicInteger());//not really needed since is threadlocal
return hsr.getRequestURI();
}
@SuppressWarnings("unchecked")
public String nextDesktopId(Desktop desktop) {
String dtid = "Desktop_"+_desktop.getAndIncrement();
System.out.println("desktop id: " + dtid);
HttpServletResponse response = (HttpServletResponse)_response.get();
response.addHeader("Desktop", dtid);// ...and here it is!
return dtid;
}
}
Couple of things I want to mention here:
- This is a simple implementation that uses the native request uri to name the page after. This implementation only considers components as being held in the first page, the generated ids will look something like /WebApplication1/index.zul_org.zkoss.zul.Button_3 not specially pretty but easily identifiable.
- Notice that I extend DHtmlLayoutServlet in this very class. That’s because I keep the response in a ThreadLocal variable, and use it later in nextDesktopId as soon as the DesktopId is generated. A cleaner implementation this might be achieved if process method on DHtmlLayoutServlet was public.
Configuration files
ZK has to be aware of our plans. Two files have to be changed: web.xml
<servlet>
<description>ZK loader for ZUML pages</description>
<servlet-name>zkLoader</servlet-name>
<servlet-class>es.indra.demo.GrinderIdGenerator</servlet-class>
<init-param>
<param-name>update-uri</param-name>
<param-value>/zkau</param-value>
</init-param>
</servlet>
And zk.xml
<system-config>
<id-generator-class>es.indra.demo.GrinderIdGenerator</id-generator-class>
</system-config>
That’s it. No more work on the ZK side
The Grinder Side
Installation
- Download Grinder 3 beta http://grinder.sf.net
- Create the grinder.properties file. this file controls the console and agent processes. A simple configuration will do, set grinder.processes, grinder.threads and grinder.runs equal to 1.
- More detailed info availavle at http://grinder.sourceforge.net/g3/properties.html
Record a Test
Grinder records the browser activity using TCPProxy. TCPProxy is a proxy process that you place between your browser and a server. Here is detailed information and even a picture http://grinder.sourceforge.net/g3/tcpproxy.html#starting-tcpproxy
The browser has to be configured to use TCPProxy (normally on port 8001). I’m pretty sure (would bet on it) you are going to have to repeat this configuration more than once, so I suggest you to use SwitchProxyTool or equivalent extension before it drives you insane.
Once the proxy is running and browser setup, recording a test can’t be more simple, just bring the application and click our only button like there’s no tomorrow. Big time fun!
Fix the grinder script
The generated script is almost ready, just a couple of tweaks have to be made. Edit grinder.py locate the procedure where GET is done and add python code to retrieve the Desktop header:
# A method for each recorded page.
def page1(self):
"""GET index.zul (request 101)."""
result = request101.GET('/WebApplication1/index.zul')
self.token_desktop = result.getHeader("Desktop")
return result
Now replace every occurrence of the string Desktop_n for the freshly created field. You should have one for each button click you made.
def page5(self):
"""POST zkau (request 501)."""
result = request501.POST('/WebApplication1/zkau',
( NVPair('dtid', self.token_desktop),
NVPair('cmd.0', 'onClick'),
NVPair('uuid.0', '/WebApplication1/index.zul_org.zkoss.zul.Button_3'),
NVPair('data.0', '26'),
NVPair('data.0', '4'),
NVPair('data.0', ''), ),
( NVPair('Content-Type', 'application/x-www-form-urlencoded'), ))
return result
The script is now ready to go.
Bring it on!
Time to start the Grinder, like always detailed info is easy to reach on the Web http://grinder.sourceforge.net/g3/getting-started.html#howtostart
- Start the Console
- Go to script tab and set grinder.py as the script to run .
- Start an Agent (you could start many of course)
- Go to the Console again and click “Send files to worker processes”
- Press play to start the worker process.
Now, in the Graphs tab, there’s different colours stuff turning on and off which is always a good sign. Furthermore, in case you don’t trust colors, check the Tomcat log to see what’s happening:
Grinder is doing the browsing. You can add more agents/threads and se how multiple threads interact with your app.
Future
Many improvements could be made to the generator to handle multiple desktops and use a persistent id generator that keep assigning the same id to a component no matter what changes happen in the zul file. I didn’t go deeper in that because: 1) components are not complete by the time the id is requested for them and 2) I’m not sure adding components to a zul page doesn’t imply new functionalities with mean record a new Grinder script anyway. I shall be delighted to hear your suggestions on zk forums.
Gerard Cristofol is a Project Manager at Indra Sistemas, Spain. He has got a degree in Computer Science, a MPM, RSA Certification, and extensive experience in Java related technologies: web applications, enterprise integration. He is now focusing on ESB service infrastructures.
Copyright © Gerard Cristofol. This article is licensed under GNU Free Documentation License. |